iT邦幫忙

2022 iThome 鐵人賽

DAY 10
0

分析完要撰寫的任務步驟後,我們來開始撰寫 ColorCodeTag 的後端程式。

本章節以 Code 實踐為主,若主要職能非實踐後端的讀者可以直接快轉往下到有圖片的地方。

基礎設置

在使用 Eclipse 創建專案前,有幾個小步驟要注意:

  • 將 Preference => General => Workspace 內將 Text file encoding 改為 UTF-8
  • 將 Preference => Java => Compiler 的 Compiler compliance level 更改為 11
  • 將 Preference => Java => Install JREs 選擇 Add => Standard VM,並將 Java Home 指到下載的 JDK 位置 (Eclipse 自從某一代後會包含一組 JDK在裡面)

以上是 Eclipse 比較需容易忘記的小步驟,關於基礎的 Spring Boot Starter 專案建立則不詳細說明,使用其他 IDE 的夥伴參考即可,而專案建構時需引入以下三個依賴:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
	<scope>runtime</scope>
	<optional>true</optional>
</dependency>
	
<dependency>
	<groupId>org.projectlombok</groupId>
	<artifactId>lombok</artifactId>
	<optional>true</optional>
</dependency>

資料夾結構

結構

ColorCodeTag follow Controller, Service, Repository 的三層式架構,如上圖所示,ColorCodeTag 的核心方法由 ColorCodeService interface 定義,並由 ColorCodeServiceImpl 實踐。另外礙於篇幅關係,則不特別展示 Kmeans 的實踐方法。

application.properties

application.properties 內定義了許多的參數,Spring Boot Start 專案創建後 properties 內部會為空,事實上是啟用了預設的參數,如 tomcat 預設的 port 為 8080,可以在 properties 重新設定,後續對 DB 的 url 等都會放置於此。

server.port=8081
#設定接收檔案大小最大為 15MB
spring.servlet.multipart.enabled=true
spring.servlet.multipart.max-file-size= 15728640
spring.servlet.multipart.max-request-size= 15728640

Service - interface

public interface ColorCodeService {

	/**
	 * 解析照片並取的顏色色碼
	 * @param picture
	 * @return
	 */
	public List<String> getColorCodeTags(MultipartFile picture);
	
}

Service - implement

實作的類別是整個 ColorCodeTag 的核心,若眼尖的小夥伴可能會在這裡發現一點小問題,這裡先賣個關子。

@Slf4j
@Service
public class ColorCodeServiceImpl implements ColorCodeService {

	@Override
	public List<String> getColorCodeTags(MultipartFile picture) {
		
        List<String> rgbList = scanWholePicture(picture);
		List<String> hexList = rgbToHexList(rgbList);

		return hexList;
	}

	/**
	 * 掃描照片並解析顏色
	 * 
	 * @param picture
	 * @return
	 */
	private List<String> scanWholePicture(MultipartFile picture) {

		float[] rgb = new float[3];

		BufferedImage bi = null;

		try {
			FileInputStream in = (FileInputStream) picture.getInputStream();
			bi = javax.imageio.ImageIO.read(in);
		} catch (IOException e) {
			log.info("照片轉換失敗");
		}

		int width = bi.getWidth();
		int height = bi.getHeight();
		int minx = bi.getMinX();
		int miny = bi.getMinY();

		ArrayList<float[]> dataSet = new ArrayList<float[]>();

		for (int i = minx; i < width; i++) {
			for (int j = miny; j < height; j++) {

				int pixel = bi.getRGB(i, j);

				rgb[0] = (pixel & 0xff0000) >> 16;
				rgb[1] = (pixel & 0xff00) >> 8;
				rgb[2] = (pixel & 0xff);

				dataSet.add(new float[] { rgb[0], rgb[1], rgb[2] });
			}
		}

		KMeansRunner kRunner = new KMeansRunner(5, dataSet);

		Set<Cluster> clusterSet = kRunner.run();
		List<String> rtnList = new ArrayList<String>();
		List<ColorTag> colorlist = new LinkedList<ColorTag>();

		for (Cluster cluster : clusterSet) {

			float[] colors = cluster.getCenter().getlocalArray();

			StringBuffer sb = new StringBuffer("");

			sb.append((int) Math.floor(colors[0]));
			sb.append(",");
			sb.append((int) Math.floor(colors[1]));
			sb.append(",");
			sb.append((int) Math.floor(colors[2]));

			String rgbString = sb.toString();
			float diatance = this.getDistance(colors[0], colors[1], colors[2]);
			ColorTag temp = new ColorTag(rgbString, diatance);

			colorlist.add(temp);

		}

		Collections.sort(colorlist);

		for (ColorTag tag : colorlist) {
			rtnList.add(tag.getColorTag());
		}
		return rtnList;
	}

	/**
	 * 計算與原點的歐氏距離
	 * @param x
	 * @param y
	 * @param z
	 * @return
	 */
	private float getDistance(float x, float y, float z) {

		float diatance = (float) Math.sqrt(Math.pow((x - 0), 2) + Math.pow((y - 0), 2) + Math.pow((x - 0), 2));

		return diatance;
	}

	/**
	 * 將 RGB 座標轉換為十六進制
	 * 
	 * @param rgbList
	 * @return
	 */
	private List<String> rgbToHexList(List<String> rgbList) {

		List<String> hexList = new ArrayList<String>();

		for (String rgb : rgbList) {

			String[] split = rgb.split(",");

			int r = Integer.parseInt(split[0]);
			int g = Integer.parseInt(split[1]);
			int b = Integer.parseInt(split[2]);

			Color rgbColor = new Color(r, g, b);
			String hexCode = "#" + Integer.toHexString(rgbColor.getRGB()).substring(2);

			hexList.add(hexCode);
		}
		return hexList;
	}
}

Service - vo

@Data
@NoArgsConstructor
@AllArgsConstructor
public class ColorTag implements Comparable<ColorTag> {

	private String colorTag;
	private float distance;
    
	public int compareTo(ColorTag colorTag) {
		return (int) (colorTag.getDistance() - this.getDistance());
	}
}

Controller

@CrossOrigin(origins = "*", allowedHeaders = "*")
@RestController
public class ColorCodeTagController {

	@Autowired
	private ColorCodeService colorCodeService;

	@PostMapping("/getColorTags")
	@ResponseBody
	public List<String> getColorTag(@RequestParam("file") MultipartFile picture) {

		List<String> rtnTagsList = colorCodeService.getColorCodeTags(picture);

		return rtnTagsList;
	}
}

Postman 測試

在後端完成後,我們來使用 postman 測試,對 localhost:8080/getColorTags 發送 HTTP POST 請求,將照片傳送到後端解析。若是大型專案,也能夠使用 Swagger 來執行 API 的測試。

postman-1

發送後,照片的色碼就成功的回傳了一組五個顏色,並依照由淺到深排序,另外來看看實體顏色呈現的樣子。

colors

在皆大歡喜前,大夥是否看到了在 200 OK 旁邊那驚為天人的響應時間:

1 m 19.44 s ! (後端的資料正在查,你名義 PM 在你後面,他非常火)

一般而言,用戶的響應時間超過兩秒就會帶來糟糕的使用者體驗,這裡我們分析一下響應時間過高的原因:

  • 傳輸檔案過大,導致傳輸時間增加
  • Code 執行某邏輯方法過久
for (int i = minx; i < width; i++) {
	for (int j = miny; j < height; j++) {

大家可能已經發現了,在實踐照片掃描 pixel 時,是掃描 "每一個" 點,並執行分群,隨著照片解析度的增加,分群的計算與迭代次數也會指數性的提升,這時負責開發的工程師可以將問題與解法測試紀錄,並及時或是於 Daily Scrum 時向團隊報告:

  • 在前端傳送照片時,先將照片的解析度降低後再傳送到後端
  • 在掃瞄照片時,不掃描全部的 pixel,而是掃描一定比例的數量

今日結語

在開發的過程中,常常會遇到非預期、或是規劃上未預料的問題,而部份的問題則與交付的程度有關,如提出的解決方法有可能會降低照片分群顏色的代表性,這時就應該由 Product Owner 進行定奪。

今天,我們實作了 ColorCodeTag 後端的程式,並用 Postman 測試成功獲取了一組五個照片的漸層顏色,也發掘了實作上帶來的問題;明天,讓我們一同審視優化的方法,來扮演 Product Owner 為這個議題定下結論,並準備結束第二期的 Sprint。


上一篇
Day9: ColorCodeTag Java 後端建置 (上)
下一篇
Day11: ColorCode Tag 專案優化與 Sprint Review
系列文
一個人也能 DevOps ? 用 Angular + Spring Boot 演示專案由開發到部署30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言